package com.yuw.niodemo;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;

/**
 * 服务器客户端通信流程图：https://www.processon.com/view/link/60af07bb6376893238dffee5
 */
public class NIOServer {

    private final int port = 9093; // 客户端和服务器端连接的端口号（定义1024之后的端口号）
    private final int BLOCK_SIZE = 4096; // channel的缓存区大小

    private Selector selector; // 服务器端的NIO的选择器
    // channel的接收缓存区
    private ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK_SIZE);
    // channel的发送缓存区
    private ByteBuffer sendBuffer = ByteBuffer.allocate(BLOCK_SIZE);

    public static void main(String[] args) {
        try {
            NIOServer nioServer = new NIOServer();
            nioServer.linstenr();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 构造方法
     */
    public NIOServer() {
        try {
            //1、打开服务器套接字通道
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 调用open方法打开
            //2、服务器配置为非阻塞模式
            serverSocketChannel.configureBlocking(false); // 设置为false
            //3、获取与通道关联的 ServerSocket对象
            ServerSocket serverSocket = serverSocketChannel.socket();
            //4、绑定端口
            serverSocket.bind(new InetSocketAddress(port));
            // 到此，获取到一个运行在服务器的指定端口的和连接管道 channel 绑定的 socket 对象
            //5、新建一个服务器服务器端的选择器，打开一个选择器
            selector = Selector.open();

            //6、将channel注册到选择器上，等待客户端的连接
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            // NIO的服务器段的初始化工作完成
            System.out.println("Server:init successfuly.");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * NIO服务器的主要处理方法：循环监听端口，进行客户端请求处理
     */
    private void linstenr() throws Exception {
        // 在NIO服务器端，使用死循环随时监听selector选择器中是否接入客户端请求，如果介入了则处理该channel
        while (true) {
            //选择一组键； 选择准备好的channel
            selector.select();
            //返回获取选择的键集：包含已经状态准备好的channel集合， SelectKey中存储了被选中的channel的所有的信息
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            if (selectionKeys.isEmpty()) {
                continue;
            }
            //遍历，循环处理请求的键集
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = (SelectionKey) iterator.next();
                iterator.remove();
                handlerKey(selectionKey);
            }

            Thread.sleep(4000);
        }

    }

    /**
     * 处理对应的  SelectionKey
     *
     * @param selectionKey
     */
    private void handlerKey(SelectionKey selectionKey) throws IOException {

        ServerSocketChannel server;
        SocketChannel client;

        // 测试此键的通道是否已准备好接受新的套接字连接
        if (selectionKey.isAcceptable()) {
            //此键对应的关联通道
            server = (ServerSocketChannel) selectionKey.channel();
            //接受到此通道套接字的连接
            client = server.accept();
            //配置为非阻塞
            client.configureBlocking(false);
            //注册到selector 等待连接
            client.register(selector, SelectionKey.OP_READ);

        } else if (selectionKey.isReadable()) {

            client = (SocketChannel) selectionKey.channel();
            //将缓冲区清空，下面读取
            receiveBuffer.clear();
            //将客户端发送来的数据读取到 buffer中
            int count = client.read(receiveBuffer);
            if (count > 0) {
                String receiveMessage = new String(receiveBuffer.array(), 0, count);
                System.out.println("Server:接受客户端的数据:" + receiveMessage);
                // 客户端的连接处理完成之后，还要把该 客户端的 channel 再次注册到选择器中，继续继续进行监听
                client.register(selector, SelectionKey.OP_WRITE);
            }
        } else if (selectionKey.isWritable()) {
            //发送消息buffer 清空
            sendBuffer.clear();
            //返回该键对应的通道
            client = (SocketChannel) selectionKey.channel();
            String sendMessage = "Send form Server...Hello... " + new Random().nextInt(100) + " .";
            //向缓冲区中写入数据
            sendBuffer.put(sendMessage.getBytes());
            //put了数据，标志位被改变
            sendBuffer.flip();
            //数据输出到通道
            client.write(sendBuffer);
            System.out.println("Server:服务器向客户端发送数据:" + sendMessage);
            // 客户端的连接处理完成之后，还要把该 客户端的 channel 再次注册到选择器中，继续继续进行监听
            client.register(selector, SelectionKey.OP_READ);
        }
    }
}
